<!DOCTYPE html>
<link rel="help" href="https://github.com/samuelgoto/idle-detection">
<title>Tests the Idle Detection API</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/test-only-api.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="resources/idle-detection-helper.js"></script>
<script>
'use strict';

promise_setup(async t => {
  await test_driver.set_permission({ name: 'idle-detection' }, 'granted', false);
  if (isChromiumBased) {
    await loadChromiumResources();
  }
})

promise_test(async t => {
  // Basic test that expects start() to call internally
  // addMonitor, which in turn return an ACTIVE state.
  expect(addMonitor).andReturn((threshold, monitorPtr) => {
      return Promise.resolve({
        state: {
          user: UserIdleState.ACTIVE,
          screen: ScreenIdleState.LOCKED
        }
      });
  });

  const controller = new AbortController();
  const detector = new IdleDetector();
  const watcher = new EventWatcher(t, detector, ["change"]);
  const initial_state = watcher.wait_for("change");

  await detector.start({ signal: controller.signal });
  await initial_state;

  assert_equals(detector.userState, "active");
  assert_equals(detector.screenState, "locked");

  controller.abort();
}, 'start()');

promise_test(async t => {
  // Verifies that an event is thrown when a change of state from IDLE to ACTIVE
  // is detected.
  expect(addMonitor).andReturn((threshold, monitorPtr) => {
      let first = Promise.resolve({
        state: {
          user: UserIdleState.ACTIVE,
          screen: ScreenIdleState.UNLOCKED
        }
      });

      t.step_timeout(() => {
        monitorPtr.update({
          user: UserIdleState.IDLE,
          screen: ScreenIdleState.UNLOCKED
        });
      }, 0);

      return first;
    });

  const controller = new AbortController();
  const detector = new IdleDetector();
  const watcher = new EventWatcher(t, detector, ["change"]);
  const initial_state = watcher.wait_for("change");

  await detector.start({ signal: controller.signal });
  await initial_state;

  // Wait for the first change in state.
  await watcher.wait_for("change");

  assert_equals(detector.userState, "idle");
  assert_equals(detector.screenState, "unlocked");

  controller.abort();
}, 'updates once');


promise_test(async t => {
  // Simulates the user being active, going idle and then going back active
  // again.
  expect(addMonitor).andReturn((threshold, monitorPtr) => {
      let first = Promise.resolve({
        state: {
          user: UserIdleState.ACTIVE,
          screen: ScreenIdleState.UNLOCKED
        }
      });

      // Updates the client once with the user idle.
      t.step_timeout(() => {
        monitorPtr.update({
          user: UserIdleState.IDLE,
          screen: ScreenIdleState.UNLOCKED
        });
      }, 0);
      // Updates the client a second time with the user active.
      t.step_timeout(() => {
        monitorPtr.update({
          user: UserIdleState.ACTIVE,
          screen: ScreenIdleState.UNLOCKED
        });
      }, 1);
      return first;
    });

  const controller = new AbortController();
  const detector = new IdleDetector();
  const watcher = new EventWatcher(t, detector, ["change"]);
  const initial_state = watcher.wait_for("change");

  await detector.start({ signal: controller.signal });
  await initial_state;

  // Waits for the first event.
  await watcher.wait_for("change");
  assert_equals(detector.userState, "idle");

  // Waits for the second event.
  await watcher.wait_for("change");
  assert_equals(detector.userState, "active");

  controller.abort();
}, 'updates twice');

promise_test(async t => {
  // Simulates a locked screen.
  expect(addMonitor).andReturn((threshold, monitorPtr) => {
      return Promise.resolve({
        state: {
          user: UserIdleState.ACTIVE,
          screen: ScreenIdleState.LOCKED
        }
      });
    });

  const controller = new AbortController();
  const detector = new IdleDetector();
  const watcher = new EventWatcher(t, detector, ["change"]);
  const initial_state = watcher.wait_for("change");

  await detector.start({ signal: controller.signal });
  await initial_state;

  assert_equals(detector.screenState, "locked");

  controller.abort();
}, 'locked screen');

promise_test(async t => {
  expect(addMonitor).andReturn((threshold, monitorPtr) => {
      return Promise.resolve({
        state: {
          user: UserIdleState.ACTIVE,
          screen: ScreenIdleState.LOCKED
        }
      });
  });

  const controller = new AbortController();
  const detector = new IdleDetector();

  let event = new Promise((resolve, reject) => {
    detector.onchange = resolve;
  });

  await detector.start({ signal: controller.signal });

  // Waits for the first event.
  await event;

  assert_equals(detector.userState, "active");
  assert_equals(detector.screenState, "locked");

  controller.abort();
}, 'IdleDetector.onchange');

promise_test(async t => {
  expect(addMonitor).andReturn((threshold, monitorPtr) => {
      return Promise.resolve({
        state: {
          user: UserIdleState.ACTIVE,
          screen: ScreenIdleState.UNLOCKED
        }
      });
    });

  const controller = new AbortController();
  const detector = new IdleDetector();

  const watcher = new EventWatcher(t, detector, ["change"]);
  const initial_state = watcher.wait_for("change");

  // Only the first call to start() is allowed.
  const start_promise = detector.start();
  await promise_rejects_dom(t, 'InvalidStateError', detector.start());
  await start_promise;

  await initial_state;
  assert_equals(detector.userState, "active");
  assert_equals(detector.screenState, "unlocked");

  // Calling abort() multiple times is safe.
  controller.abort();
  controller.abort();
  controller.abort();
  controller.abort();
}, 'Safe to call start() or stop() multiple times');

promise_test(async t => {
  expect(addMonitor).andReturn((threshold, monitorPtr) => {
      return Promise.resolve({
        state: {
          user: UserIdleState.ACTIVE,
          screen: ScreenIdleState.UNLOCKED
        }
      });
    });

  const controller = new AbortController();
  const detector = new IdleDetector();

  // Calling abort() before start() causes start() to fail.
  controller.abort();

  await promise_rejects_dom(
      t, 'AbortError', detector.start({ signal: controller.signal }));
}, 'Calling stop() after start() is a no-op');

promise_test(async t => {
  expect(addMonitor).andReturn((threshold, monitorPtr) => {
      return Promise.resolve({
        state: {
          user: UserIdleState.ACTIVE,
          screen: ScreenIdleState.UNLOCKED
        }
      });
    });

  let controller = new AbortController();
  const detector = new IdleDetector();
  const watcher = new EventWatcher(t, detector, ["change"]);
  let initial_state = watcher.wait_for("change");

  await detector.start({ signal: controller.signal });
  await initial_state;

  controller.abort();

  expect(addMonitor).andReturn((threshold, monitorPtr) => {
      return Promise.resolve({
        state: {
          user: UserIdleState.IDLE,
          screen: ScreenIdleState.LOCKED
        }
      });
    });

  // Restarting the monitor.
  controller = new AbortController();

  initial_state = watcher.wait_for("change");
  await detector.start({ signal: controller.signal });
  await initial_state;
  assert_equals(detector.userState, "idle");
  assert_equals(detector.screenState, "locked");

  controller.abort();
}, 'Calling start() after stop(): re-starting monitor.');

</script>
